package com.diao.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author chenzhidiao
 * @version 1.0
 * @date 2020/5/1 11:32
 * @description:
 */
public class RedisDistributeLock {
    private static final String LOCK_SUCCESS = "OK";
    private JedisPool jedisPool;
    private final Map<String, Boolean> DAEMON_MAP = new ConcurrentHashMap<String, Boolean>();
    private static final Long RELEASE_SUCCESS = 1L;

    public RedisDistributeLock(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public Jedis getJedis() {
        return jedisPool.getResource();
    }

    /**
     * 获取锁
     *
     * @param jedis
     * @param lockKey    锁的key
     * @param requestId  请求id
     * @param expireTime 超时时间，秒
     * @return
     */
    public boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, long expireTime) {
        //设置超时时间为expireTime毫秒，只在key不存在时创建
        /**
         * 通过SetParams对象的不同方法配置set命令
         * ex(int):设置一个单位为秒的过期时间
         * px(long):设置一个单位为毫秒的过期时间
         * nx():只有键不存在时，才对键进行操作
         * xx():只有键存在时，才对键进行操作
         */
        //set操作成功时返回OK，操作失败时返回nil
        String result = jedis.set(lockKey, requestId, new SetParams().px(expireTime).nx());

        if (result != null && LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    /**
     * 删除锁，删除之前判断是否是该线程创建的锁，避免误删，这里用Lua脚本保证判断锁和删除锁的原子性
     *
     * @param jedis
     * @param lockKey
     * @param threadId
     * @return 第一行代码是Lua脚本，功能是先获取锁对应的value值，检查是否与threadId相等，如果相等则删除锁（解锁），用Lua语言实现是为了保证上述操作的原子性
     * 第二行将Lua脚本传给jedis.eval()方法，并使参数KEYS[1]赋值为lockKey，ARGV[1]赋值为threadId。eval()方法是将Lua代码交给Redis服务端执行。
     * 在eval命令执行Lua代码的时候，Lua代码将被当成一个命令去执行，并且直到eval命令执行完成，Redis才会执行其他命令，从而保证原子性
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String threadId) {
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end)";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(threadId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }



}
